package org.jugile.web2;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.jugile.util.Blob;
import org.jugile.util.Jugile;

public class Request extends Jugile {

	private String uri;
	public String getURI() { return uri; }
	public void setURI(String v) { uri = v; }
	
//	private String ctxPath;
//	public String getCtxPath() { return ctxPath; }
//	public void setCtxPath(String v) { ctxPath = v; }
//	public boolean isServlet() { return !empty(ctxPath); }
	
	private String body;
	public void setBody(String v) { body = v; }
	public String getBody() { return body; }
	
	private String method;
	public String getMethod() { return method; }
	public void setMethod(String v) { method = v; }
	
	/**
	 * This request is for caching, not a real client request
	 */
	private boolean isForCaching = false;
	public boolean isForCaching() { return isForCaching; }
	public void setForCaching() { isForCaching = true; }
	
	
	private Properties header = new Properties();
	public String getHeader(String key) { return header.getProperty(key); }
	public void setHeader(String key, String value) { header.setProperty(key, value); }
	public List<String> getHeaderNames() {
		ArrayList<String> res = new ArrayList<String>();
		for (Object key : header.keySet()) res.add((String)key);
		return res;
	}
	public Properties getHeaders() { return header; }
	
	private Map<String,String> params = new HashMap<String,String>();
	private Map<String,List<String>> params2 = new HashMap<String,List<String>>();
	public void resetParams() { 
		params = new HashMap<String,String>(); 
		params2 = new HashMap<String,List<String>>();
	}
	public String getParam(String key) { return params.get(key); }
	public void setParam(String key, String value) {
		// trim all non null values
		if (value != null) value = value.trim();
		if (params.containsKey(key)) {
			// additional params with same key into params2
			List<String> l = params2.get(key);
			if (l == null) l = new ArrayList<String>();
			l.add(value);
			params2.put(key, l);
		} else {
			params.put(key, value);
		}
	}
	public List<String> getParamNames() {
		ArrayList<String> res = new ArrayList<String>();
		for (String key : params.keySet()) res.add(key);
		return res;
	}
	public List<String> getParamNames(String prefix) {
		ArrayList<String> res = new ArrayList<String>();
		for (String key : params.keySet()) if (key.startsWith(prefix)) res.add(key);
		return res;
	}
	public Map<String,String> getParams() { return params; }

	public boolean isMultipart() {
		String ctype = getHeader("content-type");
		if (empty(ctype)) return false;
		if (ctype.contains("multipart/form-data")) return true;
		return false;
	}
	
	private Map<String,Blob> files = new HashMap<String,Blob>();
	public Blob getFile(String key) { return files.get(key); }
	public List<String> getFileNames() {
		ArrayList<String> res = new ArrayList<String>();
		for (String key : files.keySet()) res.add(key);
		return res;
	}
		
	// ---------- parse multipart ----------
	public void parseMultipart(BufferedInputStream bis) {
		String ctype = getHeader("content-type");
		int idx = ctype.indexOf("boundary=");
		String boundary = ctype.substring(idx + "boundary=".length());
		print("boundary=" + boundary);
		print("content-length=" + getHeader("content-length"));
		List<Blob> blobs = Blob.parseMimeBuffered(bis, getHeader("content-length"), boundary);
		// TODO: optimize this, return directly Map to avoid double storage (if very large file uploads)
		for (Blob b : blobs) {
			if (b.isFile()) files.put(b.getMimeName(), b);
			else { // trim trailing "\r\n" from multipart parsed data
				String str = b.toString();
				if (str != null && str.endsWith("\r\n")) {
					str = str.substring(0, str.length()-2);
				}
				String key = b.getMimeName();
				//if (key.equals("a")) action = str;
				//if (key.startsWith("a:")) action = key.substring(2);
				setParam(key, str);
				print("setParam: " + key + "=" + str);
			}
		}
	}

	
	// ---------- parameters -----------
	public void decodeParms(String parms) {
		if ( parms == null ) return;
		setBody(parms);
		if (isJson()) return;
//		for (String hdr : getHeaderNames()) {
//			print(hdr + ":" + getHeader(hdr));
//		}
		StringTokenizer st = new StringTokenizer( parms, "&" );
		while ( st.hasMoreTokens()) {
			String e = st.nextToken();
			int sep = e.indexOf( '=' );
			if ( sep >= 0 )
				setParam( decodePercent( e.substring( 0, sep )).trim(),
						decodePercent( e.substring( sep+1 )));
		}
	}
	
	public boolean isJson() {
		return "application/json".equals(getHeader("content-type"));
	}
	
	public List<String> getParamValues(String key) {
		List<String> res = new ArrayList<String>();
		if (params.containsKey(key)) {
			res.add(params.get(key));
			if (params2.containsKey(key)) {
				res.addAll(params2.get(key));
			}
		}
		return res;
	}

	public String decodePercent( String str ) {
		try {
			Blob b = new Blob();
			for( int i=0; i<str.length(); i++ ) {
				char c = str.charAt( i );
				switch ( c ) {
				case '+':
					b.append((byte)' ');
					break;
				case '%':
					b.append((byte)Integer.parseInt( str.substring(i+1,i+3),16));
					i += 2;
					break;
				default:
					b.append((byte)c);
				break;
				}
			}
			// UTF8
			String res = getUtf8(b.getData());
			//print("decoded: " + res);
			return res;
			//return new String( sb.toString().getBytes());
		} catch( Exception e ) {
			throw new RuntimeException(e);
		}
	}

	
	private final static int BUFSIZE = 256; 
	public String readLine(BufferedInputStream bis, long size) throws IOException {
		byte[] buf = new byte[BUFSIZE];
		int pos = 0;
		for (;;) {
			if (size > 0)
				if (pos >= size) break; // content-length case POST
			int ch = bis.read();
			if (size == 0)
				if (ch == '\n' || ch < 0) break; // one line case GET
			buf[pos++] = (byte) ch;
			if (pos == buf.length) buf = Arrays.copyOf(buf, pos + BUFSIZE);
		}
		return new String(Arrays.copyOf(buf, pos), "UTF-8");
	}


}
